package mlp;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.Scanner;

import utils.RandomGenerator;

/**
 * Parses out of train file an even amount of positive and negative examples:
 * a positive example is an example labeled as the digit that the neural network
 * should recognize, and a negative one is all other digits
 */
public class UniformTrainExamples {

	final private static int CLASSES  = 10;
	private ArrayList<ArrayList<float[]>> input;
	private ArrayList<ArrayList<float[]>> output;
	private int classify;

	public UniformTrainExamples(String trainFile, int classify) {

		this.classify = classify;
		
		Scanner scan = null;
		try {
			scan = new Scanner(new File(trainFile));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}

		input = new ArrayList<ArrayList<float[]>>(CLASSES);
		output = new ArrayList<ArrayList<float[]>>(CLASSES);
		for (int i = 0 ; i < CLASSES ; ++i) {
			input.add(new ArrayList<float[]>());
			output.add(new ArrayList<float[]>());
		}
		String line;
		int cls;
		while(scan.hasNext())
		{
			line = scan.nextLine();
			cls = Integer.parseInt(line.substring(0,1));
			
			String[] inputsStr = line.substring(2).split(",");
			float[] inputs = new float[inputsStr.length];
			for(int i =0 ; i < inputsStr.length ; i++){inputs[i] = Float.parseFloat(inputsStr[i]);}
			input.get(cls).add(inputs);
			output.get(cls).add(getClsArrayBinary(cls));
		}
		scan.close();
	}
	
	private float[] getClsArrayBinary(int cls)
	{
		float[] arr = new float[1];
		arr[0] = classify == cls ? Mlp.POSITIVE : Mlp.NEGATIVE; 
		return arr;				
	}
	
	public Entry<ArrayList<float[]>, ArrayList<float[]>> getExamples(int from, int amount) {
		
		// fix number of examples
		amount = (amount >= input.get(classify).size()) ? input.get(classify).size() - 1
				: amount;
		
		// initialize return value
		ArrayList<float[]> inputs = new ArrayList<float[]>(amount);
		ArrayList<float[]> outputs = new ArrayList<float[]>(amount);
		List<Entry<float[],float[]>> examples = new ArrayList<Entry<float[],float[]>>(amount);
		
		// compose positive examples
		int numPositive = amount / 2;
		for (int i = from ; i < from + numPositive ; ++i) {			
			examples.add(new AbstractMap.SimpleEntry<float[], float[]>(input
					.get(classify).get(i), output.get(classify).get(i)));
		}
		
		// calculate how many examples of each other digit
		int numNegative = amount - numPositive;
		int numOtherDigit = numNegative / (CLASSES - 1);
		
		// choose a random digit to get the left-over
		int leftOver = -1;
		do {
			leftOver = RandomGenerator.nextInt(CLASSES);			
		} while (leftOver == classify);
		
		// add examples from all other digits
		for (int digit = 0 ; digit < CLASSES ; ++digit) {
			
			// skip digit to classify or the one which gets the leftover
			if (digit == classify || digit == leftOver) {
				continue;
			}
			
			// add examples
			for (int i = from ; i < from + numOtherDigit ; ++i) {
				examples.add(new AbstractMap.SimpleEntry<float[], float[]>(input
						.get(digit).get(i), output.get(digit).get(i)));
			}
		}
		
		// add the leftover digit
		numOtherDigit += numNegative % (CLASSES - 1);
		for (int i = from ; i < from + numOtherDigit ; ++i) {
			examples.add(new AbstractMap.SimpleEntry<float[], float[]>(input
					.get(leftOver).get(i), output.get(leftOver).get(i)));
		}
		
		// shuffle examples
		Collections.shuffle(examples);
		
		// separate inputs from outputs
		for (int i = 0 ; i < amount ; ++i) {
			inputs.add(examples.get(i).getKey());
			outputs.add(examples.get(i).getValue());
		}
		
		// return both lists
		return new AbstractMap.SimpleEntry<ArrayList<float[]>, ArrayList<float[]>>(
				inputs, outputs);
	}
}
